﻿using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using org.webguvenligi.NSecureImageLib.GraphicFileFormats.Utilities;
namespace org.webguvenligi.NSecureImageLib.GraphicFileFormats.PNG
{
    #region Textual Chunks

    /// <summary>
    /// 
    /// </summary>
    public class TextualChunk : PNGTextChunkBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TextualChunk"/> class.
        /// </summary>
        public TextualChunk()
            : base()
        {
            this.RawChunkType = System.Text.Encoding.ASCII.GetBytes("tEXt");
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="TextualChunk"/> class.
        /// </summary>
        /// <param name="pngReader">The PNG reader.</param>
        public TextualChunk(BinaryReader pngReader)
            : base(pngReader)
        {
            this.RawChunkType = System.Text.Encoding.ASCII.GetBytes("tEXt");
        }
        /// <summary>
        /// Parses the chunk data.
        /// </summary>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            this.Text = Latin1Encoding.GetString(chunkDataReader.ReadBytes((int)chunkDataReader.BytesRemaining()));
        }
        /// <summary>
        /// Gets the type of the preciding.
        /// </summary>
        /// <value>The type of the preciding.</value>
        protected override Type PrecidingType
        {
            get { return null; }
        }
        protected override byte[] WriteChunkData()
        {
            byte[] internalData = base.WriteChunkData();
            using (BinaryWriter currentWriter = new MemoryStream().CreateWriter())
            {
                currentWriter.Write(internalData);
                currentWriter.Write(Latin1Encoding.GetBytes(this.Text));
                return (currentWriter.BaseStream as MemoryStream).ToArray();
            }
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public class ZTextualChunk : PNGTextChunkBase
    {
        public ZTextualChunk()
            : base()
        {
            this.RawChunkType =
                Encoding.ASCII.GetBytes("zTXt");
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ZTextualChunk"/> class.
        /// </summary>
        /// <param name="pngReader">The PNG reader.</param>
        public ZTextualChunk(BinaryReader pngReader)
            : base(pngReader)
        {
            this.RawChunkType =
                ASCIIEncoding.ASCII.GetBytes("zTXt");
        }
        /// <summary>
        /// Parses the chunk data.
        /// </summary>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            chunkDataReader.EnsureBytes(1);
            if (chunkDataReader.ReadByte() != 0)
                new InvalidDataException("Compression Byte Cannot Be Different Then 0");
            chunkDataReader.ReadBytesEnsure(2);
            this.Text = DecompressEntry(chunkDataReader);
        }
        protected override Type PrecidingType
        {
            get { return null; }
        }
        /// <summary>
        /// Writes the chunk data.
        /// </summary>
        /// <returns></returns>
        protected override byte[] WriteChunkData()
        {
            byte[] internalData = base.WriteChunkData();
            using (BinaryWriter currentWriter = new MemoryStream().CreateWriter())
            {
                currentWriter.Write(internalData);
                currentWriter.Write((byte)0);
                currentWriter.Write(this.Text.CompressStringLatin1());
                return (currentWriter.BaseStream as MemoryStream).ToArray();
            }
        }
    }
    /// <summary>
    /// 
    /// <remarks>   
    ///Keyword:             1-79 bytes (character string)
    ///Null separator:      1 byte
    ///Compression flag:    1 byte
    ///Compression method:  1 byte
    ///Language tag:        0 or more bytes (character string)
    ///Null separator:      1 byte
    ///Translated keyword:  0 or more bytes
    ///Null separator:      1 byte
    ///Text:                0 or more bytes 
    /// </remarks>
    /// </summary>
    public class ITextualChunk : PNGTextChunkBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ITextualChunk"/> class.
        /// </summary>
        public ITextualChunk() : base() { }
        /// <summary>
        /// Initializes a new instance of the <see cref="ITextualChunk"/> class.
        /// </summary>
        /// <param name="pngReader">The PNG reader.</param>
        public ITextualChunk(BinaryReader pngReader) : base(pngReader) { }
        /// <summary>
        /// Parses the chunk data.
        /// </summary>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            chunkDataReader.EnsureBytes(1);
            int compressionByte = chunkDataReader.Read();
            chunkDataReader.EnsureBytes(1);
            int compressionMethod = chunkDataReader.Read();
            LanguageTag = chunkDataReader.ReadNullDelimitedString();
            TranslatedKeyword = chunkDataReader.ReadToNullUTF8();
            if (compressionByte == 1)
            {
                if (compressionMethod == 0)
                    this.Text = PNGTextChunkBase.DecompressEntryUTF8(chunkDataReader);
                else
                    throw new InvalidDataException();
            }
            else if (compressionByte != 1 && compressionMethod != 0)
            {
                throw new InvalidDataException();
            }
            else if (compressionByte == 0 && compressionMethod == 0)
            {
                byte[] sourceByte = chunkDataReader.ReadBytesToEnd();
                if (sourceByte.Length > 0)
                    this.Text = Encoding.UTF8.GetString(sourceByte);
            }
            CompressionByte = compressionByte;
            CompressionMethod = compressionMethod;
        }
        public int CompressionMethod
        {
            get;
            set;
        }
        public int CompressionByte
        {
            get;
            set;
        }
        const byte NULLBYTE = (byte)0;
        protected override byte[] WriteChunkData()
        {
            using (BinaryWriter currentWriter = new BinaryWriter(new MemoryStream()))
            {
                currentWriter.Write(base.WriteChunkData());
                currentWriter.Write((byte)CompressionByte);
                currentWriter.Write((byte)CompressionMethod);
                if (String.IsNullOrEmpty(LanguageTag)) LanguageTag = String.Empty;
                currentWriter.Write(Encoding.ASCII.GetBytes(LanguageTag));
                currentWriter.Write(NULLBYTE);
                if (String.IsNullOrEmpty(TranslatedKeyword)) TranslatedKeyword = String.Empty;
                currentWriter.Write(Encoding.UTF8.GetBytes(TranslatedKeyword));
                currentWriter.Write(NULLBYTE);
                if (CompressionByte == 1 && CompressionMethod == 0)
                    currentWriter.Write(Text.CompressStringUTF8());
                else
                    currentWriter.Write(Encoding.UTF8.GetBytes(Text));
                return ((MemoryStream)(currentWriter.BaseStream)).ToArray();
            }
        }
        public string TranslatedKeyword
        {
            get;
            private set;
        }
        public string LanguageTag
        {
            get;
            private set;
        }
        /// <summary>
        /// Gets the type of the preciding.
        /// </summary>
        /// <value>The type of the preciding.</value>
        protected override Type PrecidingType
        {
            get
            {
                return null;
            }
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public abstract class PNGTextChunkBase : PNGAncillaryChunkBase
    {
        /// <summary>
        /// Decompresses the entry.
        /// </summary>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        protected static string DecompressEntryUTF8(BinaryReader chunkDataReader)
        {
            using (MemoryStream deflatedEntry = new MemoryStream())
            {
                while (chunkDataReader.BytesRemaining() > 4)
                    deflatedEntry.WriteByte(chunkDataReader.ReadByte());
                deflatedEntry.Position = 0;
                using (MemoryStream deflatedStream = new MemoryStream())
                {
                    using (DeflateStream inflator = new DeflateStream(deflatedEntry, CompressionMode.Decompress))
                    {
                        int currentByte;
                        while ((currentByte = inflator.ReadByte()) != -1)
                            deflatedStream.WriteByte((byte)currentByte);
                    }
                    return Encoding.UTF8.GetString(deflatedStream.ToArray());
                }
            }
        }
        /// <summary>
        /// Decompresses the entry.
        /// </summary>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        protected static string DecompressEntry(BinaryReader chunkDataReader)
        {
            using (MemoryStream deflatedEntry = new MemoryStream())
            {
                while (chunkDataReader.BytesRemaining() > 4)
                    deflatedEntry.WriteByte(chunkDataReader.ReadByte());
                deflatedEntry.Position = 0;
                StringBuilder textInfoBuilder = new StringBuilder();
                using (DeflateStream inflator = new DeflateStream(deflatedEntry, CompressionMode.Decompress))
                {
                    int currentByte;
                    while ((currentByte = inflator.ReadByte()) != -1)
                        textInfoBuilder.Append((char)currentByte);
                }
                return textInfoBuilder.ToString();
            }
        }
        /// <summary>
        /// 
        /// </summary>
        protected readonly static System.Text.Encoding Latin1Encoding =
            Encoding.GetEncoding(28591);
        /// <summary>
        /// Gets or sets the keyword.
        /// </summary>
        /// <value>The keyword.</value>
        public string Keyword
        {
            get;
            set;
        }
        /// <summary>
        /// Gets or sets the text.
        /// </summary>
        /// <value>The text.</value>
        public string Text
        {
            get;
            set;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="PNGTextChunkBase"/> class.
        /// </summary>
        protected PNGTextChunkBase()
            : base()
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="PNGTextChunkBase"/> class.
        /// </summary>
        /// <param name="pngReader">The PNG reader.</param>
        protected PNGTextChunkBase(BinaryReader pngReader)
            : base(pngReader)
        {
        }
        /// <summary>
        /// Parses the chunk data.
        /// </summary>
        /// <remarks>
        /// The keyword must be at least one character and less than 80 characters long.
        /// </remarks>
        /// <param name="chunkDataReader">The chunk data reader.</param>
        protected override void ParseChunkData(BinaryReader chunkDataReader)
        {
            base.ParseChunkData(chunkDataReader);
            string keyword =
                chunkDataReader.ReadToNull(79);
            if (keyword.Length == 0)
                throw new InvalidDataException("Invalid Text Chunk Detected");
            this.Keyword = keyword;
        }
        protected override byte[] WriteChunkData()
        {
            if (String.IsNullOrEmpty(Keyword))
                throw new InvalidDataException();
            byte[] keyWord = ASCIIEncoding.ASCII.GetBytes(Keyword);
            if (keyWord.Length > 80)
                throw new InvalidDataException("Invalid Text Chunk Detected");
            byte[] resultArray = new byte[keyWord.Length + 1];
            Array.Copy(keyWord, 0, resultArray, 0, keyWord.LongLength);
            return resultArray;
        }
    }

    #endregion
}
